#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sikle.h"

void SString_push(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp;

  //  printf("SString_push(base:%p,sp:%d)\n", es->base, es->sp);

  if(stacel->type == STACEL_STRING) {
    *(SString*)sp = *(SString*)stacel;
    sp += sizeof(SString);
    STACEL(sp)->type = STACEL_STRING;
    es->sp += sizeof(SString) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SString_push error", NULL);
  }
}

void SString_pop(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_STRING) {
    sp -= sizeof(SString);
    *(SString*)stacel = *(SString*)sp;
    es->sp = sp - es->base;
  }
  else {
    log_msg(LOG_ERROR, "SString_pop error", NULL);
  }
}

void SString_drop(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_STRING) {
    es->sp -= sizeof(SString) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SString_drop error", NULL);
  }
}

void SString_dup(SikleState* ss)
{
  SString string;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_STRING) {
    sp -= sizeof(SString);
    string = *(SString*)sp;

    sp = es->base + es->sp;
    *(SString*)sp = string;
    sp += sizeof(SString);
    STACEL(sp)->type = STACEL_STRING;
    es->sp += sizeof(SString) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SString_dup error", NULL);
  }
}

void SString_p(SikleState* ss)
{
  SString string;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_STRING) {
    char ch;
    char* base;
    SPString sps;

    sp -= sizeof(SString);
    string = *(SString*)sp;
    sps = string.value;
    
    base = ss->string_pool->base;
    ch = base[sps.offset+sps.size];
    base[sps.offset+sps.size] = 0;
    printf("%s", base + sps.offset);
    base[sps.offset+sps.size] = ch;
  }
  else {
    log_msg(LOG_ERROR, "SString_p error", NULL);
  }
}

/* return size of the string */
int SString_to_integer(SikleState* ss)
{
  SString string;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_STRING) {
    SPString sps;
  
    sp -= sizeof(SString);
    string = *(SString*)sp;
    sps = string.value;
    
    return sps.size;
  }
  else {
    log_msg(LOG_ERROR, "SString_to_integer error", NULL);
  }

  /* nerver got here */
  return -1;
}

/* return SPString */
SPString SString_to_string(SikleState* ss)
{
  SString string;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  SPString sps;

  if(STACEL(sp)->type == STACEL_STRING) {
    sp -= sizeof(SString);
    string = *(SString*)sp;
    sps = string.value;
    
    return sps;
  }
  else {
    log_msg(LOG_ERROR, "SString_to_string error", NULL);
  }

  return sps;
}

/* if SString is not empty return true, else false */
int SString_to_boolean(SikleState* ss)
{
  return SString_to_integer(ss);
}

void SString_add(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_STRING) {
    StacelType type;
    SPString sps0;
    SPString sps1;
    SString sstring;

    type = stacel_types[STACEL_STRING];

    type.operation->pop(ss, STACEL(&sstring));
    sps1 = sstring.value;

    type = stacel_types[get_stacel_type(ss)];

    if(type.operation->to_string == NULL) {
      printf("ERROR: can not add string to %s\n", type.name);
      exit(0);
    }

    sps0 = type.operation->to_string(ss);
    type.operation->drop(ss);
    
    sstring.value = SPString_concat(ss->string_pool, sps0, sps1);

    type = stacel_types[STACEL_STRING];
    type.operation->push(ss, STACEL(&sstring));
  }
  else {
    log_msg(LOG_ERROR, "SString_add error", NULL);
  }
}

void SString_builtin_strcmp(SikleState* ss);

void SString_gt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  SString_builtin_strcmp(ss);

  type = stacel_types[STACEL_INTEGER];
  type.operation->pop(ss, STACEL(&integer));

  integer.value = integer.value > 0 ? TRUE : FALSE;

  type.operation->push(ss, STACEL(&integer));
}

void SString_ngt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  SString_builtin_strcmp(ss);

  type = stacel_types[STACEL_INTEGER];
  type.operation->pop(ss, STACEL(&integer));

  integer.value = integer.value <= 0 ? TRUE : FALSE;

  type.operation->push(ss, STACEL(&integer));
}

void SString_lt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  SString_builtin_strcmp(ss);

  type = stacel_types[STACEL_INTEGER];
  type.operation->pop(ss, STACEL(&integer));

  integer.value = integer.value < 0 ? TRUE : FALSE;

  type.operation->push(ss, STACEL(&integer));
}

void SString_nlt(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  SString_builtin_strcmp(ss);

  type = stacel_types[STACEL_INTEGER];
  type.operation->pop(ss, STACEL(&integer));

  integer.value = integer.value >= 0 ? TRUE : FALSE;

  type.operation->push(ss, STACEL(&integer));
}

void SString_equ(SikleState* ss)
{
  SInteger integer;
  StacelType type;

  SString_builtin_strcmp(ss);

  type = stacel_types[STACEL_INTEGER];
  type.operation->pop(ss, STACEL(&integer));

  integer.value = integer.value == 0 ? TRUE : FALSE;

  type.operation->push(ss, STACEL(&integer));
}

void SString_builtin_strcmp(SikleState* ss)
{
 ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  void* base = ss->string_pool->base;

  if(STACEL(sp)->type == STACEL_STRING) {
    StacelType type;
    SPString sps0;
    SPString sps1;
    SString sstring0;
    SString sstring1;
    SInteger integer;

    SString_pop(ss, STACEL(&sstring0));
    sps0 = sstring0.value;

    type_check(get_stacel_type(ss), STACEL_STRING);

    SString_dup(ss);
    SString_pop(ss, STACEL(&sstring1));
    SString_push(ss, STACEL(&sstring0));

    sps1 = sstring1.value;

    type = stacel_types[STACEL_INTEGER];
    integer.type = STACEL_INTEGER;
    integer.value = strncmp(base + sps1.offset, base + sps0.offset, 
			 MIN(sps0.size, sps1.size));

    if(integer.value == 0) {
      integer.value = sps1.size - sps0.size;
    }

    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SString_cmp error", NULL);
  }
}

void SString_register_builtin_words(SikleState* ss)
{
  register_builtin_word(ss, "strcmp", SString_builtin_strcmp);
}

StacelOperation string_ops = {
  .push = SString_push,
  .pop = SString_pop,
  .drop = SString_drop,
  .dup = SString_dup,

  .clone = NULL,
  .register_builtin_words = SString_register_builtin_words,

  .to_integer = SString_to_integer,
  .to_string = SString_to_string,
  .to_boolean = SString_to_boolean,

  .p = SString_p,

  .add = SString_add,
  .sub = NULL,
  .mul = NULL,
  .div = NULL,

  .gt = SString_gt,
  .lt = SString_lt,
  .ngt = SString_ngt,
  .nlt = SString_nlt,
  .equ = SString_equ
};
